package com.ce.socket;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.channels.SocketChannel;

import org.apache.log4j.Logger;

public class ASocket extends BsSocket implements ISocket {
	private static Logger log = Logger.getLogger(ASocket.class);
	private Socket sock;
	private DataInputStream in;
	private BufferedOutputStream out;
	private final static String EMPTY_MESSAGE = "CM20NULL00GB00000020";
	private final static byte[] BYTE_EMPTY_MESSAGE = EMPTY_MESSAGE.getBytes();
	private final static int READ_LENGTH = 1024;

	public ASocket(String host, int timeout, int connectTimeout, boolean noDelay)
			throws IOException, UnknownHostException {
		super(host, timeout, connectTimeout, noDelay);

		// get socket: default is to use non-blocking connect
		connect();

		log.info("=> Construction socket param: " + toParam());
	}

	public ASocket(String host, int port, int timeout, int connectTimeout,
			boolean noDelay) throws IOException, UnknownHostException {
		super(host, port, timeout, connectTimeout, noDelay);
		// get a socket channel
		connect();
		
		log.info("=> Construction socket param: " + toParam());
	}

	public void close() throws IOException {
		log.info( "=> Closing socket for real: " + toString() );

		boolean err = false;
		StringBuilder errMsg = new StringBuilder();

		if ( in != null ) {
			try {
				in.close();
			}
			catch( IOException ioe ) {
				log.error( "=> error closing input stream for socket: " + toString() + " for host: " + getHost() );
				log.error( ioe.getMessage(), ioe );
				errMsg.append( "=> error closing input stream for socket: " + toString() + " for host: " + getHost() + "\n" );
				errMsg.append( ioe.getMessage() );
				err = true;
			}
		}

		if ( out != null ) {
			try {
				out.close();
			}
			catch ( IOException ioe ) {
				log.error( "=> error closing output stream for socket: " + toString() + " for host: " + getHost() );
				log.error( ioe.getMessage(), ioe );
				errMsg.append( "=> error closing output stream for socket: " + toString() + " for host: " + getHost() + "\n" );
				errMsg.append( ioe.getMessage() );
				err = true;
			}
		}

		if ( sock != null ) {
			try {
				sock.close();
			}
			catch ( IOException ioe ) {
				log.error( "=> error closing socket: " + toString() + " for host: " + getHost() );
				log.error( ioe.getMessage(), ioe );
				errMsg.append( "=> error closing socket: " + toString() + " for host: " + getHost() + "\n" );
				errMsg.append( ioe.getMessage() );
				err = true;
			}
		}

		in = null;
		out = null;
		sock = null;
		if ( err )
			throw new IOException( errMsg.toString() );
	}

	public void connect() throws IOException {
		if(sock == null) {
			SocketChannel channel = SocketChannel.open();
			channel.socket().connect( new InetSocketAddress( host, port ), connectTimeout );
			this.sock = channel.socket();
			
			if (timeout >= 0)
				this.sock.setSoTimeout(this.timeout);

			// testing only
			this.sock.setTcpNoDelay(this.noDelay);

			// wrap streams
			in = new DataInputStream(new BufferedInputStream(this.sock.getInputStream()));
			out = new BufferedOutputStream(this.sock.getOutputStream());
		}

	}

	public void flush() throws IOException {
		if (sock == null || !sock.isConnected()) {
			log.error("=> attempting to write to closed socket");
			throw new IOException("=> attempting to write to closed socket");
		}
		out.flush();
	}

	public boolean isAlive() {
		if (!isConnected())
			return false;

		// try to talk to the server w/ a dumb query to ask its version
		try {
			this.write(BYTE_EMPTY_MESSAGE);
			this.flush();
			this.read();
		} catch (IOException ex) {
			log.error("=> isAlive error", ex);
			return false;
		}

		return true;
	}

	public boolean isConnected() {
		return ( sock != null && sock.isConnected() );
	}

	public byte[] read() throws IOException {
		byte[] readbuf = new byte[READ_LENGTH];
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int readSeek = 0;
		while ((readSeek = in.read(readbuf)) != -1) {
			out.write(readbuf, 0, readSeek);
			if (readSeek < READ_LENGTH)
				break;
		}
		return out.toByteArray();
	}

	public void write(byte[] data) throws IOException {
		if (sock == null || !sock.isConnected()) {
			log.error("=> attempting to write to closed socket");
			throw new IOException("=> attempting to write to closed socket");
		}
		out.write(data);
	}

	private String getHost() { return this.host+":"+this.port; }
	public String toString() {
		return ( sock == null ) ? "null" : sock.toString();
	}

	public byte[] sendData(byte[] data) throws IOException {
		// TODO Auto-generated method stub
		return null;
	}
}
